﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Gemli.Data
{
    /// <summary>
    /// A condition or filter that is used in a <see cref="DataModelQuery"/>.
    /// </summary>
    public class DataModelQueryCondition
    {
        ///<summary>
        /// Constructs a QueryCondition object using the specified
        /// parameters for describing the nature and for binding to the query.
        ///</summary>
        ///<param name="mapBy"></param>
        ///<param name="query"></param>
        public DataModelQueryCondition(FieldMappingKeyType mapBy, DataModelQuery query)
        {
            _returnQuery = query;
            FindFieldMappingBy = mapBy;
        }

        private DataModelQuery _returnQuery;

        /// <summary>
        /// Gets or sets the metadata object that describes the 
        /// entity's database field mapping which is used
        /// to construct this condition in the appropriate database
        /// context.
        /// </summary>
        protected internal virtual DataModelFieldMappingAttribute FieldMap { get; set; }

        /// <summary>
        /// Describes how a database field mapping is matched 
        /// by name--by the CLR property/field or by the database
        /// column name.
        /// </summary>
        public FieldMappingKeyType FindFieldMappingBy { get; set; }

        /// <summary>
        /// Used with the <see cref="FieldMap"/> to determine how to 
        /// identify and access the field mapping metadata.
        /// </summary>
        public string FieldMappingKey { get; set; }

        /// <summary>
        /// Describes the binary comparison type that
        /// is used on behalf of a particular property/field/column.
        /// </summary>
        public Compare CompareOp { get; set; }

        /// <summary>
        /// The value to be compared against the database.
        /// </summary>
        public object CompareValue { get; set; }

        /// <summary>
        /// Used internally for binding the syntactic sugar of
        /// the associated Query's Where clauses.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public virtual DataModelQueryCondition this[string key]
        {
            get
            {
                FieldMappingKey = key;
                return this;
            }
        }

        /// <summary>
        /// Clears out the internal reference to the originating <see cref="DataModelQuery"/>
        /// to remove the circular reference
        /// and then returns that Query to the caller.
        /// </summary>
        /// <returns></returns>
        protected virtual DataModelQuery NullifyAndReturnQuery()
        {
            var q = _returnQuery;
            if (q == null) return null;
            _returnQuery = null;
            q.QueryConditions.Add(this);
            return q;
        }

        /// <summary>
        /// Specifies a condition where a field value 
        /// is equal to the specified <paramref name="value"/>.
        /// Note: This returns a Query object for syntax chaining purposes.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public virtual DataModelQuery IsEqualTo(object value)
        {
            CheckForNull(value);
            CompareOp = Compare.Equal;
            CompareValue = value;
            return NullifyAndReturnQuery();
        }

        /// <summary>
        /// Specifies a condition where a field value 
        /// is not equal to the specified <paramref name="value"/>.
        /// Note: This returns a Query object for syntax chaining purposes.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public virtual DataModelQuery IsNotEqualTo(object value)
        {
            CheckForNull(value);
            CompareOp = Compare.NotEqual;
            CompareValue = value;
            return NullifyAndReturnQuery();
        }

        /// <summary>
        /// Specifies a condition where a field value 
        /// is greater than the specified <paramref name="value"/>.
        /// Note: This returns a Query object for syntax chaining purposes.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public virtual DataModelQuery IsGreaterThan(object value)
        {
            CheckForNull(value);
            CompareOp = Compare.GreaterThan;
            CompareValue = value;
            return NullifyAndReturnQuery();
        }

        /// <summary>
        /// Specifies a condition where a field value 
        /// is greater than or equal to the specified <paramref name="value"/>.
        /// Note: This returns a Query object for syntax chaining purposes.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public virtual DataModelQuery IsGreaterThanOrEqualTo(object value)
        {
            CheckForNull(value);
            CompareOp = Compare.GreaterThanOrEqual;
            CompareValue = value;
            return NullifyAndReturnQuery();
        }

        /// <summary>
        /// Specifies a condition where a field value 
        /// is less than the specified <paramref name="value"/>.
        /// Note: This returns a Query object for syntax chaining purposes.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public virtual DataModelQuery IsLessThan(object value)
        {
            CheckForNull(value);
            CompareOp = Compare.LessThan;
            CompareValue = value;
            return NullifyAndReturnQuery();
        }

        /// <summary>
        /// Specifies a condition where a field value 
        /// is not equal to the specified <paramref name="value"/>.
        /// Note: This returns a Query object for syntax chaining purposes.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public virtual DataModelQuery IsLessThanOrEqualTo(object value)
        {
            CheckForNull(value);
            CompareOp = Compare.LessThanOrEqual;
            CompareValue = value;
            return NullifyAndReturnQuery();
        }

        /// <summary>
        /// Specifies a condition where a field value 
        /// matches the LIKE comparison to the specified <paramref name="value"/>.
        /// Note: This returns a Query object for syntax chaining purposes.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public virtual DataModelQuery IsLike(object value)
        {
            CheckForNull(value);
            CompareOp = Compare.Like;
            CompareValue = value;
            return NullifyAndReturnQuery();
        }

        /// <summary>
        /// Specifies a condition where a field value 
        /// is null.
        /// Note: This returns a Query object for syntax chaining purposes.
        /// </summary>
        /// <returns></returns>
        public virtual DataModelQuery IsNull()
        {
            CompareOp = Compare.Null;
            return NullifyAndReturnQuery();
        }

        /// <summary>
        /// Specifies a condition where a field value 
        /// is not null.
        /// Note: This returns a Query object for syntax chaining purposes.
        /// </summary>
        /// <returns></returns>
        public virtual DataModelQuery IsNotNull()
        {
            CompareOp = Compare.NotNull;
            return NullifyAndReturnQuery();
        }

        private void CheckForNull(object value)
        {
            if (value == null)
            {
                throw new ArgumentException(
                    "DataModelQuery condition comparison was set with a null value,"
                    + " use IsNull or IsNotNull instead.");
            }
        }
    }
}
